#ifndef ROBOTMOTIONTRACK_H
#define ROBOTMOTIONTRACK_H

#include <QParallelAnimationGroup>
#include <QMap>

#include "robot.h"
#include "robotjointmotiontrack.h"
#include "actiontrack.h"

class RobotAction;


/**
 * @brief Animates the robot. Inherits QParallelAnimationGroup.
 *
 * **NOTE: PLEASE LOOK AT actiontrack.h FOR AN EXPLANATION OF THE ActionTrack
 * INTERFACE**
 *
 * The robot motion "track" is a parallel animation group of animations
 * for each of the robot's joints.  It is responsible for animating the
 * robot and providing access to the animation.
 *
 * We allow each joint subtrack to have key frames placed independently
 * from one another, but our current action file format doesn't take
 * advantage of this ability (see the ActionFileWriter class for more
 * information).  The set of "active" joints are those joints that
 * have one or more key frames defined for this motion.
 *
 * The motion track emits signals at certain points during playback
 * of the motion.  The are emitted at each "frame" (timeChanged()) and
 * at each key frame (the keyFrame() signals).
 *
 * The motion track allows editing of the motion by key frames.  There
 * are two methods for manipulating these key frames (depending on what
 * convention you want to use).  You can edit each joint separately, or
 * you can insert, move, and delete key frames that encompass every joint.
 * For example, calling insertKeyFrame without a joint ID will create
 * a key frame for every joint, interpolating each new value linearly.
 * However, if you call insertKeyFrame with a joint ID, then only that
 * joint will have a key frame inserted there.  The current timeline widget
 * uses the generic/group functions to do its editing; it doesn't give access
 * to individiual joint key frames.
 *
 * When the group functions are called, they place the robot in batch mode
 * so that each little change doesn't cause signals to be emitted from the
 * robot, but rather the signals are emitted when all the little changes
 * are done (see Robot::startBatch())
 *
 * This class basically gives access to the functions that are found
 * in the RobotJointMotionTrack class, so for specific notes on the
 * behavior of functions, it is recommended that you look in RobotJointMotionTrack
 * for details.
 *
*/
class RobotMotionTrack : public QParallelAnimationGroup, public RobotActionTrack
{
    Q_OBJECT

private:
    Robot* _robot;
    int _curKeyFrame, _oldDuration;
    QMap<int, RobotJointMotionTrack*> _jointTracks;

    /**
     * @brief Checks for the existance of a keyframe at the indicated time.
     *
     * @param msec
     */
    bool keyFrameExists(int msec);
    /**
      * @brief Gets the joint track associated with the joint, and creates it if none exists.
      * The return value is guaranteed to be non-null.
      *
      * @param jointID
      */
    RobotJointMotionTrack* ensureJointTrack(int jointID);
    /**
      * @brief Gets the joint track associated with the joint, or NULL if no track exists for the joint.
      *
      * jointID
      */
    RobotJointMotionTrack* getJointTrack(int jointID) const;

protected:    
    /**
     * @brief Reimplemented to smooth out the animation and emit the timeChanged signal.
     *
     * @param currentTime
     */
    void updateCurrentTime(int currentTime);

public:
    /**
     * @brief Constructor.  Creates a new RobotMotionTrack.  The motion
     * track requires knowledge of what robot it is animating.
     *
     * @param robot A pointer to the robot that is going to be animated.
     * @param parent
    */
    explicit RobotMotionTrack(Robot* robot, QObject *parent = 0);
    ~RobotMotionTrack();

    void setRobot(Robot* robot);

    /**
     * @brief Sets the duration of this motion.
     *
     * This calls RobotJointMotionTrack::setDuration() for each of
     * the joint motion tracks; see that function for more details on
     * its behavior.
     *
     * @param msec The new duration of the motion track, in msec.
    */
    void setDuration(int msec);

    //Functions for editing the key frames of the motion
    /**
     * @brief Inserts a key frame for every joint at the given time.
     *
     * Inserting a key frame automatically calculates the value
     * based on a linear interpolation between the previous and next
     * key frames. Inserting past the end of the motion will increase
     * its duration with all joints being set to 0.
     *
     * Returns a MotionKeyFrame struct representing the key frame
     * that was inserted.  The MotionKeyFrame has a vector of
     * joint ID/value pairs which describe what key frame values were inserted
     * at each joint.  If the value is INVALID_KEYFRAME, then no
     * key frame was inserted at the joint because a one already existed or
     * there was some other error.  See RobotJointMotionTrack::insertKeyFrameAt()
     * for more details.
     *
     * @param msec The timestamp of the key frame to be inserted, in milliseconds.
     */
    MotionKeyFrame insertKeyFrameAt(int msec);

    /**
     * @brief Inserts a key frame for a specific joint at the given time.
     *
     * Returns the value (angle) of the key frame inserted.  See
     * RobotJointMotionTrack::insertKeyFrameAt() for more details.
     *
     * @param msec The timestamp of the key frame to be inserted, in milliseconds.
     * @param jointID The joint where the key frame is to be inserted.
     */
    float insertKeyFrameAt(int msec, int jointID);

    /**
     * @brief Sets the start key frame value (angle) for a given joint.
     *
     * See RobotJointMotionTrack::setStartValue() for more details.
     *
     * @param jointID The joint for which the start value is to be set.
     * @param theta The angle at which to set the joint.
     */
    void setStartValue(int jointID, float theta);

    /**
     * @brief Sets the end key frame value (angle) for a joint.
     *
     * See RobotJointMotionTrack::setEndValue() for more details.
     *
     * @param jointID The joint for which the end value is to be set.
     * @param theta The angle at which to set the key frame.
     */
    void setEndValue(int jointID, float theta);

    /**
     * @brief Sets the key frame value (angle) at a given time for a given joint.
     *
     * If the key frame doesn't exist, one is created.  Returns the value of
     * the key frame, which may have been corrected for min/max values.  For
     * more information on this functions behavior, see
     * RobotJointMotionTrack::setKeyFrameAt().
     *
     * @param msec The timestamp, in milliseconds, of the key frame to be set.
     * @param jointID The joint for which the key frame is to be set.
     * @param theta The angle at which to set the key frame.
     */
    float setKeyFrameAt(int msec, int jointID, float theta);

    /**
     * @brief Deletes the key frames for every joint at a given time.
     *
     * Returns a MotionKeyFrame struct representing the key frame that was
     * deleted (which is useful for restoring the deleted key frame, etc.)
     * Deleting the end key frame will clip the motion duration to the
     * previous key frame.  The key frame at 0 cannot be deleted. See
     * RobotJointMotionTrack::deleteKeyFrameAt() for more details on the
     * behavior of deleting key frames.
     *
     * @param msec The timestamp, in milliseconds, of the key frame to be deleted.
     */
    MotionKeyFrame deleteKeyFrameAt(int msec);

    /**
     * @brief Deletes the key frame at a given time for a given joint.
     *
     * Returns the value (angle) of the key frame that was deleted, or
     * INVALID_KEYFRAME if there was an error.  See RobotJointMotionTrack::deleteKeyFrameAt()
     * for more details.
     *
     * @param msec The timestamp, in milliseconds, of the key frame to be deleted.
     * @param jointID The joint from which the key frame is to be deleted.
     */
    float deleteKeyFrameAt(int msec, int jointID);

    /**
     * @brief Moves the key frames for every joint from one timestamp to another.
     *
     * Moving a key frame past the end of the motion will set that key frame
     * as the end key frame and increase the duration of the motion.  See
     * RobotJointMotionTrack::moveKeyFrame() for more details.
     *
     * @param msec The current timestamp of the key frame to move (in milliseconds).
     * @param newMsec The new timestamp for the key frame (in milliseconds).
     */
    void moveKeyFrame(int msec, int newMsec);

    /**
     * @brief Moves the key frame for a specific joint from one timestamp to another.
     *
     * @param msec The current timestamp of the key frame to move (in milliseconds).
     * @param jointID The joint for which the key frame is to be moved.
     * @param newMsec The new timestamp for the key frame (in milliseconds).
     */
    void moveKeyFrame(int msec, int jointID, int newMsec);

    //Functions for information about the key frames
    /**
     * @brief Returns the key frame value for a given time and joint or
     * INVALID_KEYFRAME if the key frame doesn't exist.
     *
     * @param msec The timestamp, in milliseconds, of the key frame.
     * @param jointID The joint that contains the key frame.
     */
    float keyValueAt(int msec, int jointID);

    /**
     * @brief Returns the list of key frames for this motion. Each key frame is
     * returned as a MotionKeyFrame struct.
     */
    QList<MotionKeyFrame> keyFrames();

    /**
     * @brief Returns the timestamp, in milliseconds, of the next key frame based
     * on the current time of the motion.
     *
     */
    int nextKeyFrame() const;

    /**
     * @brief Returns the timestamp, in milliseconds, of the previous key frame
     * based on the current time of the motion.
     *
     */
    int prevKeyFrame() const;

    //Functions for information about the motion track
    /**
     * @brief Returns the IDs of all the joints this motion can animate.
     *
     */
    QList<int> jointIDs() const;

    /**
     * @brief Returns the IDs of all the joints this motion is animating.
     *
     */
    QList<int> activeJointIDs() const;

    friend QTextStream &operator<<(QTextStream &out, const RobotAction &action);
    friend QDataStream &operator<<(QDataStream &out, const RobotAction &action);


signals:
    /**
     * @brief Signal emitted every time a key frame is reached
     * by a joint.
     *
     * @param msec The timestamp of the key frame (in msec).
     * @param jointID The joint that reached the key frame.
    */
    void keyFrame(int msec, int jointID);

    /**
     * @brief A generic key frame signal.  It is emitted only once
     * when one or more joints hit a key frame at a given timestamp.
     *
     * @param msec The timestamp of the key frame (in msec).
    */
    void keyFrame(int msec);

    /**
     * @brief Emitted at each frame of the animation with the current
     * time.
     *
     * @param msec The current time of the animation (in msec).
    */
    void timeChanged(int msec);

    /**
     * @brief Emitted whenever a key frame is created by one of the
     * key frame editing functions.
     *
     * It is only emitted once for a given timestamp (for example, if joint 3
     * contains a key frame at 100 msec, but joint 2 does not, calling
     * insertKeyFrameAt(100,2) does not fire this signal.)  However,
     * the KeyFrameSet signal will be emitted regardless if a key frame
     * already existed at the given time.
     *
     * @param msec The timestamp of the key frame(s) created.
     */
    void keyFrameCreated(int msec);

    /**
     * @brief Emitted when a key frame has been successfully set
     * at a certain time/joint in the motion.
     *
     * @param msec The time (in msec) that the key frame was set.
     * @param jointID The ID of the joint where the key frame was set.
     * @param theta The angle associated with the key frame.
    */
    void keyFrameSet(int msec, int jointID, float theta);

    /**
     * @brief Emitted when the last key frame from a specific time has
     * been deleted. This signal is for group editing.
     *
     * @param msec The timestamp from which all key frames have been removed.
     */
    void keyFrameDeleted(int msec);

    /**
     * @brief Emitted when a key frame is deleted from a specific joint.
     *
     * @param msec The timestamp (in milliseconds) of the key frame that was deleted.
     * @param jointID The joint from which the key frame was removed.
     */
    void keyFrameDeleted(int msec, int jointID);

    /**
     * @brief Emitted when a key frame is moved from one timestamp to another. This
     * signal is for group editing.
     *
     * @param msec The original timestamp (in milliseconds) of the key frame that was moved.
     * @param newMsec The new timestamp (in milliseconds) of the key frame that was moved.
     */
    void keyFrameMoved(int msec, int newMsec);

    /**
     * @brief Emitted when a key frame is moved from one timestamp to another.  This
     * signal is emitted for a specific joint.
     *
     * @param msec The original timestamp (in milliseconds) of the key frame that was moved.
     * @param jointID The joint that contains the key frame moved.
     * @param newMsec The new timestamp (in milliseconds) of the key frame that was moved.
     */
    void keyFrameMoved(int msec, int jointID, int newMsec);

    /**
     * @brief Emitted when the motion's duration has changed.
     *
     * @param newMsec The new duration, in milliseconds, of the motion.
     * @param oldMsec The old duration, in milliseconds, of the motion.
     */
    void motionDurationChanged(int newMsec, int oldMsec);


private slots:
    void onTrackKeyFrame(int msec);
    void onTrackDurationChanged(int msec);

public slots:

};

#endif // ROBOTMOTIONTRACK_H
